복잡하지만 바뀌지 않는 일정한 패턴을 갖는 작업 흐름이 존재하고 그중 일부분만 자주 바꿔서 사용해야 하는 경우에 사용하는 구조를 템플릿/콜백패턴이라고 부른다.

컨텍스트 = 탬플릿
익명 내부 클래스로 만들어지는 오브젝트 = 콜백 

템플릿콜백의 동작원리

탬플릿은 고정된 작업 흐름을 가진 코드를 재사용한다는 의미에서 붙인 이름이고 콜백은 템플릿 안에서 호출되는 것을 목적으로 만들어진 오브젝트를 말한다.

템플릿/콜백의 특징

템플릿콜백 패턴의 콜백은 보통 단일 메소드 인터페이스를 사용한다. 템플릿의 작업 흐름 중 특정 기능을 위해 한 번 호출되는 경우가 일반적이기 때문이다. 일반적으로 하나의 메소드를 가진 인터페이스를 구현한 익명 내부 클래스로 만들어진다고 보면 된다.

콜백 인터페이스의 메소드에는 보통 파라미터가 있는데, 이 파라미터는 템플릿의 작업 흐름 중에 만들어지는 컨텍스트 정보를 전달받을 때 사용된다.

클라이언트가 템플릿 메소드를 호춣하면서 콜백 오브젝트를 전달하는 것은 메소드 레벨에서 일어나는 DI다. 일반적인 DI라면 템플릿에 인스턴스 변수를 만들어두고 사용할 의존 오브젝트를 수정자 메소드로 받아서 사용할 것이다. 반면에 템플릿/콜백 방식에서는 매번 메소드 단위로 사용할 오브젝트를 새롭게 전달받는다는 것이 특징이다. 콜백 오브젝트가 내부 클래스로서 자신을 생성한 클라이언트 메소드 내의 정보를 직접 참조한다는 것도 템플릿/콜백 메소드의 고유한 특징이다.

편리한 콜백의 재활용

템플릿/콜백 방식으로 클라이언트인 DAO의 메소드는 간결해지고 최소한의 데이터 액세스 로직만 갖고 있게 되었다. 하지만 코드가 불편하다!

콜백의 분리와 재활용

언제나 그랬듯이 중복될 가능성이 있는 자주 바뀌지 않는 부분을 분리하여 메소드로 빼낸다.

콜백과 템플릿의 결합

메소드로 빼넨 콜백을 하나의 클래스에서만 사용하기에는 아까우므로, DAO가 공유할 수 있는 템플릿 클래스 안으로 옮긴다.

public class JdbcContext{
    ...
    public void executeSql(final String query) throws SQLException {
        workWithStatementStrategy(new StatementStrategy() {
            public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                return c.prepareStatement(query);
            }
        });
    }
 }

결국 JdbcContext 안에 클라이언트와 템플릿, 콜백이 모두 함께 공존하면서 동작하는 구조가 되었다.

템플릿/콜백의 응용

고정된 작업 흐름을 갖고 있으면서 여기저기서 자주 반복되는 코드가 있다면, 중복되는 코드를 분리할 방법을 생각해보는 습관을 기르자. 가장 전형적인 try/catch/finally 블록을 사용하는 코드를 예로 들어 이를 이해해 보자.

테스트와 try/catch/finally

public class Calculator{
    public Integer calcSum(String filepath) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(filepath));
            Integer sum = 0 ;
            String line = null;

            while((line = br.readline())!=null) {
                sum += Integer.valueOf(line);
            }

            return sum;
        } catch (IOException e) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (br != null) {
                try { br.close(); }
                catch (IOException e) { System.out.println(e.getMessage()); }
            }
        }
    }
}

중복의 제거와 템플릿/콜백 설계

여러 가지 방식으로 처리하는 기능이 계속 추가될 수 있게 대응 할 수 있도록 템플릿/콜백 패턴을 적용한다. 적용할 때는, 1. 템플릿에 담을 반복되는 작업 흐름은 어떤 것인지 2. 템플릿이 콜백에게 전달해줄 내부의 정보는 무엇인지
3. 콜백이 템플릿에게 돌려줄 내용은 무엇인지 4. 템플릿이 작업을 마친 뒤 클라이언트에게 전달해줄 것이 무엇인지

콜백ㅡ템플렛이 각각 전달하는 내용이 무엇인지 파악하는 게 가장 중요하다.

BufferedReaderCallback을 사용하는 템플릿 메소드


 public Integer fileReadTemplate(String filepath, BufferedReaderCallback callback) throws IOException {
        BufferedReader br = null;

        try {
            br = new BufferedReader(new FileReader(filepath));
            int ret = callback.doSomthingWithReaer(br);
            return ret;
        } catch (IOException e) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (br != null) {
                try { br.close(); }
                catch (IOException e) { System.out.println(e.getMessage()); }
            }
        }
    }

템플릿/콜백을 적용한 calcSum()메소드


    public Integer calcSum(String filepath) throws IOException {
       BufferedReaderCallback sumCallback = new BufferedReaderCallback() {
           public Integer doSomthingWithReaer(BufferedReader br) throws IOException {
               Integer sum = 0 ; 
               String line = null;
               while((line = br.readLine())!=null){
                   sum += Integer.valueOf(line);
               }

               return sum;
           }
       };
       return fileReadTemplate(filepath, sumCallback);
    }

템플릿/콜백의 재설계

템플릿을 사용한 콜백들 사이에 또 공통적인 패턴이 발견되었다. 템플릿과 패턴을 찾아낼 때는, 변하는 코드의 경계를 찾고 그 경계를 사이에 두고 주고받는 일정한 정보가 있는지 확인한다. 이를 이용하여 템플릿을 다시 만들어 보자.

public Integer lineReadTemplate(String filepath, LineCallback callback, int initVal) throws IOException {
        BufferedReader br = null;
        try{
            br = new BufferedReader(new FileReader(filepath));

            int res = initVal;
            String line = null;

            while((line = br.readLine())!=null){
                res = callback.doSomethingWithLine(line, res);
            }
            return res;
        } catch (IOException e) {
            System.out.println(e.getMessage());
            throw e;
        } finally {
            if (br != null) {
                try { br.close(); }
                catch (IOException e) { System.out.println(e.getMessage()); }
            }
        }
    }

public Integer calcSum(String filepath) throws IOException {
        LineCallback sumCallback = new LineCallback(){
            public Integer doSomethingWithLine(String line, Integer value) {
                return value + Integer.valueOf(line);
            }
        };
        return lineReadTemplate(filepath, sumCallback, 0);
    }

제네릭스를 이용한 콜백 인터페이스

제네릭을 활용하면 더 강력한 템플릿/콜백 구조를 만들 수 있다. Integer 타입으로 고정되어 있는 것을 다양하게 하기 위해, 제네릭을 활용한다.

```java public T lineReadTemplate(String filepath, LineCallback callback, T initVal) throws IOException { BufferedReader br = null; try{ br = new BufferedReader(new FileReader(filepath));

        T res = initVal;
        String line = null;

        while((line = br.readLine())!=null){
            res = callback.doSomethingWithLine(line, res);
        }
        return res;
    } catch (IOException e) {
        System.out.println(e.getMessage());
        throw e;
    } finally {
        if (br != null) {
            try { br.close(); }
            catch (IOException e) { System.out.println(e.getMessage()); }
        }
    }
}

```java
public Integer calcSum(String filepath) throws IOException {
        LineCallback<Integer> sumCallback = new LineCallback<Integer>(){
            public Integer doSomethingWithLine(String line, Integer value) {
                return value + Integer.valueOf(line);
            }
        };
        return lineReadTemplate(filepath, sumCallback, 0);
    }

Issues

Integer/ int,
String / StringBuffer / "" 의 차이
resource의 path 찾기